package com.gome.gop.mybatis.plugin;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gome.gop.common.Page;

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }) })
public class PageHelper implements Interceptor {
	private static final Logger logger = LoggerFactory.getLogger(PageHelper.class);

	@SuppressWarnings("unchecked")
	public Object intercept(Invocation invocation) throws Throwable {
		// 分页
		Page<?> page = null;
		if (invocation.getTarget() instanceof RoutingStatementHandler) {
			RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
			MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
			// 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环

			// 可以分离出最原始的的目标类)

			while (metaStatementHandler.hasGetter("h")) {
				Object object = metaStatementHandler.getValue("h");
				metaStatementHandler = SystemMetaObject.forObject(object);
			}
			// 分离最后一个代理对象的目标类

			while (metaStatementHandler.hasGetter("target")) {
				Object object = metaStatementHandler.getValue("target");
				metaStatementHandler = SystemMetaObject.forObject(object);
			}
			MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
			// 分页信息if (localPage.get() != null) {

			BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
			boolean isPage = false;

			if (boundSql.getParameterObject() instanceof Map) {
				Map<String, Object> parameterMap = (Map<String, Object>) boundSql.getParameterObject();
				if (boundSql.getParameterObject() instanceof MapperMethod.ParamMap) {
					parameterMap = (MapperMethod.ParamMap<Object>) boundSql.getParameterObject();
				}
				if (parameterMap != null) {
					for (Object parameter : parameterMap.values()) {
						if (parameter instanceof Page) {
							isPage = true;
							page = (Page<?>) parameter;
							break;
						}
					}
				}

			} else if (boundSql.getParameterObject() instanceof Page) {
				isPage = true;
				page = (Page<?>) boundSql.getParameterObject();
			}

			if (!isPage) {
				return invocation.proceed();
			}
			// 分页参数作为参数对象parameterObject的一个属性

			String sql = boundSql.getSql();
			// 重写分页sql
			String pageSql = sql;
			if (page.isLimitPage()) {
				pageSql = buildPageSql(sql, page);
			}

			metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
			Connection connection = (Connection) invocation.getArgs()[0];
			// 重设分页参数里的总页数等

			setPageParameter(sql, connection, mappedStatement, boundSql, page);
			// 将执行权交给下一个拦截器
			return invocation.proceed();
		}
		return invocation.proceed();
	}


	/**
	 * 
	 * 只拦截这两种类型的
	 * 
	 * <br>
	 * StatementHandler
	 * 
	 * <br>
	 * ResultSetHandler
	 *
	 * 
	 * 
	 * @param target
	 * 
	 * @return
	 * 
	 */
	public Object plugin(Object target) {
		if (target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	public void setProperties(Properties properties) {
	}

	/**
	 * 
	 * 修改原SQL为分页SQL
	 *
	 * 
	 * 
	 * @param sql
	 * 
	 * @param page
	 * 
	 * @return
	 * 
	 */
	private String buildPageSql(String sql, Page<?> page) {
		StringBuilder pageSql = new StringBuilder(100);
		pageSql.append(sql);
		// 判断是分页还是统计总行数
		if (page.isLimitPage()) {
			pageSql.append(" limit " + page.startIndex() + "," + page.getPageSize());
		}
		return pageSql.toString();
	}

	/**
	 * 
	 * 获取总记录数
	 *
	 * 
	 * 
	 * @param sql
	 * 
	 * @param connection
	 * 
	 * @param mappedStatement
	 * 
	 * @param boundSql
	 * 
	 * @param page
	 * 
	 */
	private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement, BoundSql boundSql, Page<?> page) {
		// 记录总记录数

		String countSql = " SELECT count(1) " + removeSelect(removeOrders(sql));
		PreparedStatement countStmt = null;
		ResultSet rs = null;
		try {
			countStmt = connection.prepareStatement(countSql);
			// 由于该物理分页不支持mybatis的<foreach>标签，so对该分页做一下更改
			BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
			for (ParameterMapping mapping : boundSql.getParameterMappings()) {
				String prop = mapping.getProperty();
				if (boundSql.hasAdditionalParameter(prop)) {
					countBS.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
				}
			}
			// BoundSql countBS = new
			// BoundSql(mappedStatement.getConfiguration(), countSql,
			// boundSql.getParameterMappings(), boundSql.getParameterObject());

			setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
			rs = countStmt.executeQuery();
			int totalCount = 0;
			if (rs.next()) {
				totalCount = rs.getInt(1);
			}
			page.setTotal(totalCount);
			int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
			page.setPageTotal(totalPage);
		} catch (SQLException e) {
			logger.error("Ignore this exception", e);
		} finally {
			try {
				rs.close();
			} catch (SQLException e) {
				logger.error("Ignore this exception", e);
			}
			try {
				countStmt.close();
			} catch (SQLException e) {
				logger.error("Ignore this exception", e);
			}
		}
	}

	/**
	 * 
	 * 代入参数值
	 *
	 * 
	 * 
	 * @param ps
	 * 
	 * @param mappedStatement
	 * 
	 * @param boundSql
	 * 
	 * @param parameterObject
	 * 
	 * @throws SQLException
	 * 
	 */
	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
		parameterHandler.setParameters(ps);
	}

	/**
	 * <b>移除orderby</b>
	 * <p>
	 * </p>
	 * 
	 * @param sql
	 * @return
	 */
	protected String removeOrders(String sql) {
		Pattern p = Pattern.compile("ORDER\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(sql);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, "");
		}
		m.appendTail(sb);
		return sb.toString();
	}

	/**
	 * <b>去除sql语句中select子句</b>
	 * <p>
	 * </p>
	 * 
	 * @param hql
	 * @return
	 */
	private static String removeSelect(String sql) {
        
        String regex = "[\\s*|\t|\r|\n][F|f][R|r][O|o][M|m][\\s*|\t|\r|\n]";
        Matcher matcher = Pattern.compile(regex).matcher(sql);
        
        int beginPos = 0;
        if (matcher.find()) {
            beginPos = sql.indexOf(matcher.group());
        }
        if (beginPos <= 0) {
			throw new IllegalArgumentException(" sql : " + sql + " must has a keyword 'from'");
		}
		return sql.substring(beginPos);
	}
}